// Base64Decoder.java
// $Id: Base64Decoder.java,v 1.6 2000/08/16 21:37:48 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package rpc.codec.base64;

import java.io.*;

/**
 * Decode a BASE64 encoded input stream to some output stream.
 * This class implements BASE64 decoding, as specified in the
 * <a href="http://ds.internic.net/rfc/rfc1521.txt">MIME specification</a>.
 *
 * @see rpc.codec.base64.Base64Encoder
 */

public class Base64Decoder {
private static final int BUFFER_SIZE = 1024;

InputStream in = null;
OutputStream out = null;
boolean stringp = false;

private void printHex(int x) {
  int h = (x & 0xf0) >> 4;
  int l = (x & 0x0f);
  System.out.print
      ((new Character((char) ((h > 9) ? 'A' + h - 10 : '0' + h))).toString()
       + (new Character((char) ((l > 9) ? 'A' + l - 10 : '0' + l))).toString());
}

private void printHex(byte buf[], int off, int len) {
  while (off < len) {
    printHex(buf[off++]);
    System.out.print(" ");
  }
  System.out.println("");
}

private void printHex(String s) {
  byte bytes[];
  try {
    bytes = s.getBytes("ISO-8859-1");
  }
  catch (UnsupportedEncodingException ex) {
    throw new RuntimeException(this.getClass().getName() +
                               "[printHex] Unable to convert" +
                               "properly char to bytes");
  }
  printHex(bytes, 0, bytes.length);
}

private final int get1(byte buf[], int off) {
  return ((buf[off] & 0x3f) << 2) | ((buf[off + 1] & 0x30) >>> 4);
}

private final int get2(byte buf[], int off) {
  return ((buf[off + 1] & 0x0f) << 4) | ((buf[off + 2] & 0x3c) >>> 2);
}

private final int get3(byte buf[], int off) {
  return ((buf[off + 2] & 0x03) << 6) | (buf[off + 3] & 0x3f);
}

private final int check(int ch) {
  if ((ch >= 'A') && (ch <= 'Z')) {
    return ch - 'A';
  }
  else if ((ch >= 'a') && (ch <= 'z')) {
    return ch - 'a' + 26;
  }
  else if ((ch >= '0') && (ch <= '9')) {
    return ch - '0' + 52;
  }
  else {
    switch (ch) {
      case '=':
        return 65;
      case '+':
        return 62;
      case '/':
        return 63;
      default:
        return -1;
    }
  }
}

/**
 * Do the actual decoding.
 * Process the input stream by decoding it and emiting the resulting bytes
 * into the output stream.
 *
 * @throws IOException           If the input or output stream accesses failed.
 * @throws Base64FormatException If the input stream is not compliant
 *                               with the BASE64 specification.
 */

public void process()
    throws IOException, Base64FormatException
{
  byte buffer[] = new byte[BUFFER_SIZE];
  byte chunk[] = new byte[4];
  int got = -1;
  int ready = 0;

  fill:
  while ((got = in.read(buffer)) > 0) {
    int skiped = 0;
    while (skiped < got) {
      // Check for un-understood characters:
      while (ready < 4) {
        if (skiped >= got)
          continue fill;
        int ch = check(buffer[skiped++]);
        if (ch >= 0)
          chunk[ready++] = (byte) ch;
      }
      if (chunk[2] == 65) {
        out.write(get1(chunk, 0));
        return;
      }
      else if (chunk[3] == 65) {
        out.write(get1(chunk, 0));
        out.write(get2(chunk, 0));
        return;
      }
      else {
        out.write(get1(chunk, 0));
        out.write(get2(chunk, 0));
        out.write(get3(chunk, 0));
      }
      ready = 0;
    }
  }
  if (ready != 0)
    throw new Base64FormatException("Invalid length.");
  out.flush();
}

/**
 * Do the decoding, and return a String.
 * This methods should be called when the decoder is used in
 * <em>String</em> mode. It decodes the input string to an output string
 * that is returned.
 *
 * @throws RuntimeException      If the object wasn't constructed to
 *                               decode a String.
 * @throws Base64FormatException If the input string is not compliant
 *                               with the BASE64 specification.
 */

public String processString()
    throws Base64FormatException
{
  if (!stringp)
    throw new RuntimeException(this.getClass().getName()
                               + "[processString]"
                               + "invalid call (not a String)");
  try {
    process();
  }
  catch (IOException e) {
  }
  String s;
//	try {
//	    s = ((ByteArrayOutputStream) out).toString("ISO-8859-1") ;
//	} catch (UnsupportedEncodingException ex) {
//	    throw new RuntimeException(this.getClass().getName() +
//				       "[processString] Unable to convert" +
//				       "properly char to bytes");
  s = ((ByteArrayOutputStream) out).toString();
  return s;
}

/**
 * Create a decoder to decode a String.
 *
 * @param input The string to be decoded.
 */

public Base64Decoder(String input) {
  byte bytes[];
  try {
    bytes = input.getBytes("ISO-8859-1");
  }
  catch (UnsupportedEncodingException ex) {
    throw new RuntimeException(this.getClass().getName() +
                               "[Constructor] Unable to convert" +
                               "properly char to bytes");
  }
  this.stringp = true;
  this.in = new ByteArrayInputStream(bytes);
  this.out = new ByteArrayOutputStream();
}

/**
 * Create a decoder to decode a stream.
 *
 * @param in  The input stream (to be decoded).
 * @param out The output stream, to write decoded data to.
 */

public Base64Decoder(InputStream in, OutputStream out) {
  this.in = in;
  this.out = out;
  this.stringp = false;
}

//    /**
//     * Test the decoder.
//     * Run it with one argument: the string to be decoded, it will print out
//     * the decoded value.
//     */
//
//    public static void main (String args[]) {
//	if ( args.length == 1 ) {
//	    try {
//		Base64Decoder b = new Base64Decoder (args[0]) ;
//		System.out.println ("["+b.processString()+"]") ;
//	    } catch (Base64FormatException e) {
//		System.out.println ("Invalid Base64 format !") ;
//		System.exit (1) ;
//	    }
//	} else if ((args.length == 2) && (args[0].equals("-f"))) {
//	    try {
//		FileInputStream in = new FileInputStream(args[1]) ;
//		Base64Decoder b = new Base64Decoder (in, System.out);
//		b.process();
//	    } catch (Exception ex) {
//		System.out.println("error: " + ex.getMessage());
//		System.exit(1) ;
//	    }
//	} else {
//	    System.out.println("Base64Decoder [strong] [-f file]");
//	}
//	System.exit (0) ;
//    }
}
